#!/usr/bin/python  
# -*- coding:utf8 -*-  

# ---------------------------------------------------------------------------------------------------
# 获取对象信息
'''
获取对象信息
当我们拿到一个对象的引用时，如何知道这个对象是什么类型、有哪些方法呢？
使用type()
'''

def fn():
    pass
print(type(fn))
print(type(abs))

print(type(b'a'))
print(type('a'))
print(type('abc') == str)
print()
#判断基本数据类型可以直接写int，str等，但如果要判断一个对象是否是函数怎么办？可以使用types模块中定义的常量：

#print(type(fn)==function)       #NameError: name 'function' is not defined
import types
print(type(fn)==types.FunctionType)
print(type(abs)==types.BuiltinFunctionType)
print(type(lambda x: x)==types.LambdaType)
print(type((x for x in range(10)))==types.GeneratorType)
print()

"""
对于class的继承关系来说，使用type()就很不方便。我们要判断class的类型，可以使用isinstance()函数。
如果继承关系是： object -> Animal -> Dog -> Husky
"""
class Animal(object):
    pass
class Dog(Animal):
    pass
class Husky(Dog):
    pass
a = Animal()  
d = Dog() 
h = Husky() 

print(isinstance(h, Husky))
print(isinstance(h, Dog))
print(isinstance(h, Animal))
print(isinstance(h, object))
print()

#并且还可以判断一个变量是否是某些类型中的一种，比如下面的代码就可以判断是否是list或者tuple：
isinstance([1, 2, 3], (list, tuple))
isinstance((1, 2, 3), (list, tuple))

"""
如果要获得一个对象的所有属性和方法，可以使用dir()函数，
它返回一个包含字符串的list，比如，获得一个str对象的所有属性和方法：
"""
print(dir('abc'))
"""
类似__xxx__的属性和方法在Python中都是有特殊用途的，比如__len__方法返回长度。
在Python中，如果你调用len()函数试图获取一个对象的长度，
实际上，在len()函数内部，它自动去调用该对象的__len__()方法，所以，下面的代码是等价的：
"""

print(len('abc'))
print('abc'.__len__())

#我们自己写的类，如果也想用len(myObj)的话，就自己写一个__len__()方法：
class Husky(Dog):
    def __len__(self):
        return 100

h = Husky() 
print(len(h))

#剩下的都是普通属性或方法，比如lower()返回小写的字符串：
print('ABC'.lower())
print()
#仅仅把属性和方法列出来是不够的，配合getattr()、setattr()以及hasattr()，我们可以直接操作一个对象的状态：
class Husky(Dog):
    def __init__(self):
        self.speed = 10
    def pursuitSpeed(self):
        return self.speed * 3
#测试属性
h = Husky();
print(hasattr(h,'speed'))       # 是否有speed属性
print(hasattr(h,'weight'))      # 是否有weight属性

setattr(h, 'weight', 19)        # 设置一个属性'weight'
 
print(hasattr(h,'weight'))      # 是否有weight属性
print(getattr(h,'weight'))      # 获取属性'weight'

"""
#如果试图获取不存在的属性，会抛出AttributeError的错误：
getattr(h, 'height')          # 获取属性'height'
Traceback (most recent call last):
  File "E:\Codes\Git\life-is-short\OOP_Inherit.py", line 229, in <module>
    getattr(h, 'height')          # 获取属性'height'
AttributeError: 'Husky' object has no attribute 'height'

#可以传入一个default参数，如果属性不存在，就返回默认值：
"""
print(getattr(h, 'height',40))      
print()
#也可以获得对象的方法：
print(hasattr(h,'pursuitSpeed'))        # 是否有pursuitSpeed属性
print(getattr(h,'pursuitSpeed'))        # 是否有weight属性
fn = getattr(h,'pursuitSpeed')
print(fn())                            # 调用fn()与调用h.pursuitSpeed()是一样的

'''
#setattr只能设置和添加属性,没办法设置和添加方法(说是这么说,但是最好这么做).....
给实例绑定方法可以用 MethodType
'''
def strollSpeed(self): # 定义一个函数作为实例方法
    return self.speed/2
    
from types import MethodType
h.strollSpeed = MethodType(strollSpeed,h) 
print(h.strollSpeed())       
print(getattr(h,'strollSpeed')())       
#但是，给一个实例绑定的方法，对另一个实例是不起作用的. 
#为了给所有实例都绑定方法，可以给class绑定方法.Husky.strollSpeed = strollSpeed

"""
小结

通过内置的一系列函数，我们可以对任意一个Python对象进行剖析，拿到其内部的数据。
要注意的是，只有在不知道对象信息的时候，我们才会去获取对象信息。
如果可以直接写：
sum = obj.x + obj.y
就不要写：
sum = getattr(obj, 'x') + getattr(obj, 'y')
一个正确的用法的例子如下：
"""
def readImage(fp):
    if hasattr(fp, 'read'):
        return readData(fp)
    return None
"""
假设我们希望从文件流fp中读取图像，我们首先要判断该fp对象是否存在read方法，
如果存在，则该对象是一个流，如果不存在，则无法读取。hasattr()就派上了用场。

请注意，在Python这类动态语言中，根据鸭子类型，有read()方法，
不代表该fp对象就是一个文件流，它也可能是网络流，也可能是内存中的一个字节流，
但只要read()方法返回的是有效的图像数据，就不影响读取图像的功能。
"""

# ---------------------------------------------------------------------------------------------------
# 实例属性和类属性

"""
由于Python是动态语言，根据类创建的实例可以任意绑定属性。

给实例绑定属性的方法是通过实例变量，或者通过self变量：
"""
print('\n')
class Student(object):
    def __init__(self, name):
        self.name = name

s = Student('Bob')
s.score = 90

#但是，如果Student类本身需要绑定一个属性呢？可以直接在class中定义属性，这种属性是类属性，归Student类所有：
class Student(object):
    name = 'Student'
#当我们定义了一个类属性后，这个属性虽然归类所有，但类的所有实例都可以访问到。来测试一下：

s = Student() # 创建实例s
print(s.name) # 打印name属性，因为实例并没有name属性，所以会继续查找class的name属性
print(Student.name) # 打印类的name属性

s.name = 'Michael' # 给实例绑定name属性
print(s.name) # 由于实例属性优先级比类属性高，因此，它会屏蔽掉类的name属性
print(Student.name) # 但是类属性并未消失，用Student.name仍然可以访问

del s.name # 如果删除实例的name属性 
print(s.name) # 再次调用s.name，由于实例的name属性没有找到，类的name属性就显示出来了
'''
从上面的例子可以看出，在编写程序的时候，千万不要把实例属性和类属性使用相同的名字，
因为相同名称的实例属性将屏蔽掉类属性，但是当你删除实例属性后，再使用相同的名称，访问到的将是类属性。
'''


# ---------------------------------------------------------------------------------------------------
# 使用@property
'''
在绑定属性时，如果我们直接把属性暴露出去，虽然写起来很简单，但是，没办法检查参数，导致可以把成绩随便改：
g = GraduateStudent()
g.score = 9999
这显然不合逻辑。
为了限制score的范围，可以通过一个set_score()方法来设置成绩，
再通过一个get_score()来获取成绩，这样，在set_score()方法里，就可以检查参数：
'''
class Student(object):

    def get_score(self):
         return self._score

    def set_score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value
'''
但是，上面的调用方法又略显复杂，没有直接用属性这么直接简单。

有没有既能检查参数，又可以用类似属性这样简单的方式来访问类的变量呢？
对于追求完美的Python程序员来说，这是必须要做到的！

还记得装饰器（decorator）可以给函数动态加上功能吗？对于类的方法，装饰器一样起作用。
Python内置的@property装饰器就是负责把一个方法变成属性调用的：
'''      
class Student(object):

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value
'''
@property的实现比较复杂，我们先考察如何使用。
把一个getter方法变成属性，只需要加上@property就可以了，
此时，@property本身又创建了另一个装饰器@score.setter，
负责把一个setter方法变成属性赋值，于是，我们就拥有一个可控的属性操作：
'''
s = Student()
s.score = 60 # OK，实际转化为s.set_score(60)
print(s.score) # OK，实际转化为s.get_score()
'''
s.score = 9999
Traceback (most recent call last):
  ...
ValueError: score must between 0 ~ 100!

注意到这个神奇的@property，我们在对实例属性操作的时候，
就知道该属性很可能不是直接暴露的，而是通过getter和setter方法来实现的。

还可以定义只读属性，只定义getter方法，不定义setter方法就是一个只读属性：
'''
class Student(object):

    @property
    def birth(self):
        return self._birth

    @birth.setter
    def birth(self, value):
        self._birth = value

    @property
    def age(self):
        return 2015 - self._birth

'''
上面的birth是可读写属性，而age就是一个只读属性，
因为age可以根据birth和当前时间计算出来。

小结

@property广泛应用在类的定义中，可以让调用者写出简短的代码，
同时保证对参数进行必要的检查，这样，程序运行时就减少了出错的可能性。
'''

#请利用@property给一个Screen对象加上width和height属性，以及一个只读属性resolution：
class Screen(object):
    
    @property
    def width(self):
        return self._width
        
    @width.setter
    def width(self,value):
        self._width = value
    
    @property
    def height(self):
        return self._height
        
    @height.setter
    def height(self,value):
        self._height = value
    
    @property
    def resolution(self):
        return self._width * self._height
    
s = Screen()
s.width = 1024
s.height = 768
print(s.resolution)
assert s.resolution == 786432, '1024 * 768 = %d ?' % s.resolution   #如果结果不正确,会返回错误




